﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using AlgoritimoGenetico.dados;
using AlgoritimoGenetico.operadores;
using System.IO;
using AlgoritimoGenetico.utils;

namespace AlgoritimoGenetico.ambiente
{
  public class Ambiente
  {
    #region Construtor
    public Ambiente
        (
            int tamanhoPopulacao,
            int maximoEpocas,
            double taxaCruzamento,
            double taxaMutacao,
            double taxaElitismo,
            List<byte> configuracaoUsuario,
            DateTime mesReferencia
        )
    {
      this.TamanhoPopulacao = tamanhoPopulacao;
      this.Populacao = new List<Cromossomo>();
      this._GenesPool = new List<Gene>();
      this.ConfiguracaoUsuario = configuracaoUsuario;
      this.MesReferencia = mesReferencia;

      this.MaximoEpocas = maximoEpocas;
      this._EpocaAtual = 0;

      if (taxaCruzamento < 0 || taxaCruzamento > 1)
        throw new Exception("Taxa de cruzamento deve estar entre 0 e 1");
      this.TaxaCruzamento = taxaCruzamento;

      if (taxaMutacao < 0 || taxaMutacao > 1)
        throw new Exception("Taxa de mutação deve estar entre 0 e 1");
      this.TaxaMutacao = taxaMutacao;

      if (taxaElitismo < 0 || taxaElitismo > 1)
        throw new Exception("Taxa de elitismo deve estar entre 0 e 1");
      this.TaxaElitismo = taxaElitismo;

      RepositorioCidades.GerarCidades(this.MesReferencia);
        }
        #endregion


    #region Atributos
    public int _EpocaAtual = -1;
    private List<Cromossomo> _ProximaPopulacao = null;
    private List<Cromossomo> _Pais = null;
    private List<Gene> _GenesPool = null;
    private float _FitnessTotalGenes = -1;
    #endregion

    #region Propriedades
    public int TamanhoPopulacao { get; private set; }
    public int TamanhoRota { get; private set; }
    public int MaximoEpocas { get; private set; }
    public double TaxaCruzamento { get; private set; }
    public double TaxaMutacao { get; private set; }
    public double TaxaElitismo { get; private set; }
    public List<byte> ConfiguracaoUsuario { get; private set; }
    public DateTime MesReferencia { get; private set; }
    public List<Cromossomo> Populacao { get; private set; }
    Cromossomo melhor;
    #endregion

    #region Métodos
    #region Publicos
    public string Run()
    {
        _GerarPoolGenes();
        _AvaliarPoolGenes(this.ConfiguracaoUsuario);

        Populacao = _GerarPopulacaoInicial();
      
        melhor = Populacao[0];
        while (true)
        {
            _ProximaPopulacao = Utils.Clone(Populacao);

            if (_AvaliarPopulacao())
              break;

            _SelecionarPais();
            _AplicarElitismo();
            _AplicarCruzamento();
            _Mutacao();
          
             if (this._ProximaPopulacao.Exists(c => c.Genes.Count > 10))
                  throw new Exception("Tamanho dos Genes no Proxima populacao maior que 10!!!!");

             if (this.Populacao.Exists(c => c.Genes.Count > 10))
                  throw new Exception("Tamanho dos Genes no Populacao maior que 10!!!!");

            this.Populacao = Utils.Clone(this._ProximaPopulacao);
            this._EpocaAtual++;
        }

        this.Populacao.Sort(delegate(Cromossomo g1, Cromossomo g2) { return g2.Fitness.CompareTo(g1.Fitness); });


        Cromossomo resultado1 = Populacao[0];
        Cromossomo resultado2 = Populacao[1];// = _PegarMelhorCromossomo(this.Populacao);

        return "### CROMOSSOMO 1 ###\n" + resultado1.ToString() + "\n\n### CROMOSSOMO 2 ###\n" + resultado2.ToString() + "\n\nAMBIENTE\nTamanho População: " + this.Populacao.Count;
    }
    #endregion
    #region Privados
    /// <summary>
    /// Gera o pool de genes a partir das cidades do repositório
    /// </summary>
    private void _GerarPoolGenes()
    {
        if (RepositorioCidades.Cidades.Count <= 0)
            throw new Exception("Sem cidades para computar!");

        if (RepositorioCidades.Cidades[0].Caracteristicas.Count <= 0)
            throw new Exception("Cidades sem caracteristicas!");

        this._GenesPool = new List<Gene>(RepositorioCidades.Cidades.Count);
        Gene gene = null;

        foreach (Cidade cidade in RepositorioCidades.Cidades)
        {
            gene = new Gene(cidade);
            _GenesPool.Add(gene);
        }
    }
    /// <summary>
    /// Avalia e ordena os genes do GenesPool
    /// </summary>
    /// <param name="configUsuario"></param>
    private void _AvaliarPoolGenes(List<byte> configUsuario)
    {
        this._FitnessTotalGenes = 0;
        foreach (Gene gene in this._GenesPool)
        {
            gene.Fitness = Operadores.Avaliar(gene, configUsuario);
            this._FitnessTotalGenes += gene.Fitness;
        }

        this._GenesPool.Sort(delegate(Gene g1, Gene g2) { return g2.Fitness.CompareTo(g1.Fitness); });
    }

    private List<Cromossomo> _GerarPopulacaoInicial()
    {
        List<Cromossomo> popInicial = new List<Cromossomo>(this.TamanhoPopulacao);
        Cromossomo cromo = null;
        for (int i = 0; i < this.TamanhoPopulacao; i++)
        {
            cromo = null;
            cromo = _GerarCromossomoNumGenesAleatorio();
            popInicial.Add(cromo);
        }

        foreach (Cromossomo cromossomo in popInicial)
        {
            Utils.AgruparCidadesCromossomo(cromossomo);
        }
        return popInicial;
    }
    /// <summary>
    /// Gera um cromossomo com genes variando de 1 a 10
    /// </summary>
    /// <returns></returns>
    private Cromossomo _GerarCromossomoNumGenesAleatorio()
    {
        Cromossomo cromossomo = new Cromossomo();
   
        int numGenes = Utils.Rand.Next(1, 11);
        cromossomo.Genes = new List<Gene>(numGenes);

        for (int i = 0; i < numGenes; i++)
        {
            cromossomo.Genes.Add(Operadores._Roleta(this._GenesPool, this._FitnessTotalGenes));
        }

        return cromossomo;
    }
    
    private Cromossomo _PegarMelhorCromossomo(List<Cromossomo> pop)
    {
      Cromossomo c = pop[0];

      foreach (Cromossomo c2 in pop)
      {
        if (c2.Fitness > c.Fitness)
        {
          c = c2;
        }
      }

      return c;
    }
    private bool _AvaliarPopulacao()
    {
      _CalcularTodosFitness();
      if (this._EpocaAtual == MaximoEpocas /*|| _IgualdadePopulacao()*/)
        return true;
      return false;
    }

    private void _CalcularTodosFitness()
    {
      foreach (Cromossomo cromossomo in _ProximaPopulacao)
        cromossomo.Fitness = Operadores.Avaliar(cromossomo, this.ConfiguracaoUsuario);
    }

    private void _SelecionarPais()
    {
      _Pais = Utils.Clone(_ProximaPopulacao);
      _Pais.Sort(delegate(Cromossomo c1, Cromossomo c2) { return c2.Fitness.CompareTo(c1.Fitness); });
    }

    private void _AplicarElitismo()
    {
      _ProximaPopulacao = Operadores.Elitismo(_ProximaPopulacao, this.TaxaElitismo);
    }

    private void _AplicarCruzamento()
    {
      Cromossomo filho = null;
      bool flagParada = false;
      while (true)
      {
        for (int i = 0; i < _Pais.Count - 2; i++)
        {
          for (int j = i; j < _Pais.Count - 2; j++)
          {
            if (Utils.Rand.Next(1, 101) <= 5)
            {
              filho = Operadores.Cruzamento(_Pais[j], _Pais[j + 1], _GenesPool, this._FitnessTotalGenes);
              if (filho.Genes.Count > 10)
                  throw new Exception("Numero de Genes no cruzamento estão maior que 10!!!!");
              _ProximaPopulacao.Add(filho);
              if (_ProximaPopulacao.Count >= TamanhoPopulacao - 1) flagParada = true;
            }
            if (flagParada) break;
          }
          if (flagParada) break;
          if (i >= _Pais.Count - 2) i = 0;
        }
        if (flagParada) break;
      }
    }

    private void _Mutacao()
    {
      foreach (Cromossomo c in _ProximaPopulacao)
      {
          double nextRand = Utils.Rand.NextDouble();
          if (nextRand <= this.TaxaMutacao)
              Operadores.Mutacao(c);
          Utils.AgruparCidadesCromossomo(c);
      }
    }
    
    private void _TruncarPopulacao()
    {
      int end = _ProximaPopulacao.Count();
      if (end - this.TamanhoPopulacao > 0) // pra evitar que ele tente truncar qdo a prox populacao for menor que a atual
        _ProximaPopulacao.RemoveRange(this.TamanhoPopulacao, end - this.TamanhoPopulacao);
    }

    private bool _IgualdadePopulacao()
    {
      double taxaIgualdade = 0.8;

      int taxa = (int)(taxaIgualdade * _ProximaPopulacao.Count);

      List<Cromossomo> iguais = new List<Cromossomo>();
      for (int i = 0; i < _ProximaPopulacao.Count; i++)
      {
        for (int j = i + 1; j < _ProximaPopulacao.Count; j++)
        {
          if (_ProximaPopulacao[i].Equals(_ProximaPopulacao[j]))
            iguais.Add(_ProximaPopulacao[i]);
        }
        if (iguais.Count >= taxa)
          return true;
      }
      return false;
    }

    public override string ToString()
    {
      string populacao = string.Empty;

      foreach (Cromossomo cromossomo in this.Populacao)
        populacao = string.Concat(populacao, cromossomo.ToString());

      return populacao;
    }
    #endregion
    #endregion
  }
}